Skip to content

Actualizaciones después de probar documentos#100

Open
eduranm wants to merge 4 commits into
scieloorg:mainfrom
eduranm:tk_72
Open

Actualizaciones después de probar documentos#100
eduranm wants to merge 4 commits into
scieloorg:mainfrom
eduranm:tk_72

Conversation

@eduranm
Copy link
Copy Markdown
Contributor

@eduranm eduranm commented May 21, 2026

O que esse PR faz?

  • Agrega código faltante en function_docx
  • Existen documentos en .docx sin propiedades del texto, como tamaño de letra, para estos casos se define un default de 12 para identificar secciones.
  • Permite identificar salto de línea entre Título abstract y texto de abstract.
  • Función para revisar orden alfabético en párrafos, funciona para identificar texto posterior a las referencias.
  • Cambio en la manera de detectar título referencias.

Onde a revisão poderia começar?
Por commits

Como este poderia ser testado manualmente?
Levantar el entorno;
Cargar documento;

Algum cenário de contexto que queira dar?
N/A

Screenshots
N/A

Quais são tickets relevantes?
#72

Referências
N/A

@Rossi-Luciano
Copy link
Copy Markdown
Contributor

Revisão — problemas encontrados

🔴 Bug crítico — append_fragment quebra com < literal no texto

Ao processar bn-2025-1828.docx, a task get_labels falhou com:

lxml.etree.Error: StartTag: invalid element name, line 1, column 661

  File "markup_doc/labeling_utils.py", line 1088, in append_fragment
    wrapper = etree.XML(f"<_wrap_>{clean}</_wrap_>")

O registro foi marcado como PROCESSED mas text_xml ficou NULL — o XML não foi gerado.

O problema está em append_fragment: textos com < literal (ex: comparações como a < b, < 50%) passam por remove_unpaired_tags sem serem escapados, porque o regex dessa função só reconhece sequências no formato <nome...>. O & solto já é escapado (linha 1072), mas o < literal não recebe tratamento equivalente.

Solução sugerida — adicionar após remove_unpaired_tags:

clean = re.sub(r'<(?![/a-zA-Z_])', '&lt;', clean)

Detalhes completos: issue #101


🟡 Bug de perda silenciosa — proccess_special_content descarta referências sem aviso

Quando search_special_id não encontra o elemento no corpo do documento, retorna None. A linha seguinte:

"reftype": dict_type.get(id[0].lower(), 'other')

lança TypeError: 'NoneType' object is not subscriptable, capturado pelo except genérico que imprime no log e continua — a referência à figura/tabela/equação é descartada silenciosamente.

Solução sugerida:

id = search_special_id(data_body, label)
if id is None:
    continue

Detalhes completos: issue #101


🟢 Observação — resp_json = {} em get_data_first_block

A inicialização antecipada de resp_json = {} é uma boa correção — sem ela, a função lançaria UnboundLocalError quando o status code não fosse 200. Vale mencionar explicitamente na descrição do PR o que motivou essa mudança.

@Rossi-Luciano
Copy link
Copy Markdown
Contributor

🔴 Bug adicional — & literal em conteúdo de tabela quebra o parse XML

Após corrigir localmente os bugs da issue #101, um terceiro erro surgiu ao reprocessar bn-2025-1828.docx:

lxml.etree.Error: xmlParseEntityRef: no name, line 1, column 2518

  File "markup_doc/xml.py", line 560, in get_xml
    tabla_element = etree.XML(node_table_text)

Em xml.py:560, o conteúdo da tabela (node_table_text) vai direto para etree.XML() após apenas remover quebras de linha e <br>. Um & literal no texto da tabela (sem ser entidade válida como &amp;) causa a falha.

O append_fragment já escapa & solto, mas o trecho de tabelas não passa por essa função — vai direto ao parse.

Solução sugerida — adicionar escape após a limpeza existente (linha 557):

node_table_text = re.sub(r'&(?!\w+;|#\d+;)', '&amp;', node_table_text)

@Rossi-Luciano
Copy link
Copy Markdown
Contributor

⚠️ Limitação identificada — DOCX sem estilos de parágrafo estruturados

Ao validar o XML gerado a partir de bn-2025-1828.docx, o XML foi gerado com sucesso (bem formado, 84KB), mas com conteúdo incompleto:

Elemento Status Motivo
Abstract (EN/PT) ✅ presente
<article-title> ❌ ausente
<title> das seções ❌ vazio
<ref-list> ❌ vazia (40+ referências no DOCX)
<contrib-group> ❌ vazio

A causa raiz é que o documento usa style='Normal' em praticamente todos os parágrafos — título do artigo, títulos de seção ("Introduction", "Material and Methods", etc.), autores e referências bibliográficas. O documento tem apenas um Heading 1 em todo o texto ("Ethics", parágrafo 152 de 270).

O pipeline não consegue distinguir um título de seção de um parágrafo comum quando ambos têm o mesmo estilo. Isso não é um bug — é uma limitação de documentos que não seguem o template esperado pela ferramenta.

Sugestão: documentar explicitamente quais estilos de parágrafo o DOCX deve usar para que o pipeline identifique corretamente título do artigo, seções e referências (ex: Heading 1, Heading 2, References). Isso pode ser comunicado como pré-requisito ao usuário antes do upload.

@Rossi-Luciano
Copy link
Copy Markdown
Contributor

🔴 Novos problemas identificados — bn-2025-1870.docx

Arquivo testado: fixtures/bn-2025-1870.docx


Bug 1 — Corpo principal do artigo ausente

O DOCX possui 4 seções com Heading 1 que formam o conteúdo central do artigo:

Parágrafo Estilo Seção
[32] Heading 1 Introduction
[45] Heading 1 Material and Methods
[50] Heading 1 Results
[59] Heading 1 Discussion

Nenhuma dessas seções — nem seu conteúdo — aparece no XML gerado. O pipeline processou apenas o trecho a partir de "Supplementary Material" (parágrafo [64], style='Normal'), ignorando completamente tudo o que vem antes.


Bug 2 — <p> como filhos diretos de <body> e seções sem conteúdo interno

O XML gerado apresenta dois problemas estruturais relacionados:

  1. Seções como "Acknowledgments", "Authors' Contribution", "Conflicts of Interest" e "Ethics" aparecem como <p> diretos dentro de <body> — estrutura inválida em SPS
  2. As <sec> geradas ("Supplementary Material", "Associate Editor", "Data Availability") contêm apenas o <title> internamente; o conteúdo correspondente ficou como <p> irmão da <sec> em vez de filho:
<body>
  <sec>
    <title>Supplementary Material</title>  <!-- conteúdo deveria estar aqui dentro -->
  </sec>
  <p>The following online material is available for this article:</p>  <!-- mas ficou aqui fora -->
  ...
</body>

O que funcionou corretamente neste documento

  • <history> com datas de recebimento (22/12/2025) e aceite (21/04/2026) preenchidas corretamente ✅

@Rossi-Luciano
Copy link
Copy Markdown
Contributor

🔍 Revisão de tasks.py e function_docx.py


tasks.py — Observações

1. comes_before_or_equal funciona apenas para referências em ordem alfabética (ABNT)

A lógica de detecção de pós-referências compara o texto normalizado dos parágrafos lexicograficamente (p1 <= p2). Funciona para referências ABNT (ordenadas por sobrenome do autor, A→Z), mas falha para estilo Vancouver (numeradas por ordem de citação): referências fora de ordem alfabética seriam classificadas como pós-referências e perdidas do <ref-list>.

Vale documentar essa limitação ou verificar se a ferramenta tem escopo definido para ABNT.

2. state['references'] com semântica dupla

O mesmo flag é usado para dois estados distintos: "estamos na seção de referências" (linha 242) e "já passamos das referências" (linha 278). Isso dificulta a leitura e manutenção da máquina de estados.


function_docx.py — Problemas

1. 🟡 type sobrescreve built-in do Python + regressão no .get() (linha 374)

# antes
type_matches = [(key, objt) for key, objt in list_types.items() if objt.get('numId') == numId]

# depois
type = [(key, objt) for key, objt in list_types.items() if objt['numId'] == numId]

Dois problemas: type é um built-in do Python e não deve ser usado como nome de variável; e a troca de objt.get('numId') por objt['numId'] introduz risco de KeyError caso alguma entrada de list_types não tenha a chave numId.

2. 🟡 type[0] sem guarda de lista vazia (linha 387)

if type[0][1][str(0)] == 'decimal':

Se nenhum tipo de lista corresponder ao numId, type é uma lista vazia e type[0] lança IndexError. O código original protegia com if type_matches and type_matches[0][1].get(str(0)) == 'decimal'.

3. 🔴 Tabelas após item de lista podem ser silenciosamente descartadas

if not is_numPr: content.append(obj) (linha 598) está fora do bloco if not obj_image: e do elif isinstance(element, CT_Tbl):. Para elementos de tabela, is_numPr não é resetado dentro do branch de tabela — herda o valor da iteração anterior. Se o parágrafo imediatamente anterior era um item de lista (is_numPr = True), a condição é False e a tabela é silenciosamente descartada do content.

Correção sugerida: resetar is_numPr = False no início do branch de tabela, ou mover o content.append(obj) para dentro de cada branch:

elif isinstance(element, CT_Tbl):
    ...
    obj['type'] = 'table'
    obj['table'] = table_data
    content.append(obj)  # append direto, sem depender de is_numPr

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants